/*
 * TMap Designer - A Thematic Map design software
 * Copyright (C) 2006 World Health Organization
 * http://www.who.int
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
package who.tmap.digitizer.impl;

import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.Scanner;

import org.apache.log4j.Logger;
import org.bushe.swing.event.EventBus;

import who.tmap.digitizer.DigitizerEntry;
import who.tmap.digitizer.DigitizerService;
import who.tmap.ui.event.DigitizerInput;

/**
 * @see http://rxtx.qbang.org/wiki/index.php/Using_RXTX_In_Eclipse
 * @author mathieu bolletta
 *
 */
public class DigitizerServiceImpl //extends Observable 
								  implements DigitizerService, SerialPortEventListener {
	
	private static final int DEFAULT_XMIN =0;
	private static final int DEFAULT_XMAX =6000;
	private static final int DEFAULT_YMIN =0;
	private static final int DEFAULT_YMAX =6000;	
	
	private static final int DEFAULT_BAUDRATE =9600;
	private static final int DEFAULT_PARITY =SerialPort.PARITY_NONE;
	private static final int DEFAULT_DATABITS =SerialPort.DATABITS_8;
	private static final String DEFAULT_COMPORT ="COM1";
	private static final int DEFAULT_STOPBITS =SerialPort.STOPBITS_1;
	
	private static final String DEFAULT_FIELD_SEPARATOR =",";
	private static final String DEFAULT_LINE_SEPARATOR ="\r\n";
	private static final String DEFAULT_NEW_POINT_BUTTON ="1";
	private static final String DEFAULT_CLOSE_POLYGON_BUTTON ="2";
	
	private CommPortIdentifier port;
	private InputStream inputStream;
	private String data;
	
	private SerialPort serialPort;
	
	private Logger logger = Logger.getLogger(DigitizerServiceImpl.class); 
	
	private Thread thread;

	private boolean running =false;
	
	private double xMin;
	private double yMin;
	private double xMax;
	private double yMax;
	
	private String fieldSeparator;
	private String lineSeparator;
	private String newPointButton;
	private String closePolygonButton;
	
	private String communicationPort;
	private int baudRate;
	private int dataBits;
	private int parity;
	private int stopBits;
	
	private String bytesAlrdyRead ="";
	
	private static DigitizerService instance;
	
	private DigitizerServiceImpl() {		
		initWithDefaultValues();
	}
	
	public static synchronized DigitizerService getInstance() {
		if ( instance == null ) {
			instance =new DigitizerServiceImpl();
		}
		
		return instance;
	}

	private void initWithDefaultValues() {
		baudRate =DEFAULT_BAUDRATE;
		parity =DEFAULT_PARITY;
		dataBits =DEFAULT_DATABITS;
		communicationPort = "COM1";
		stopBits =DEFAULT_STOPBITS;
		
		xMin =DEFAULT_XMIN;
		yMin =DEFAULT_YMIN;
		xMax =DEFAULT_XMAX;
		yMax =DEFAULT_YMAX;
		
		fieldSeparator =DEFAULT_FIELD_SEPARATOR;
		lineSeparator =DEFAULT_LINE_SEPARATOR;
		newPointButton =DEFAULT_NEW_POINT_BUTTON;
		closePolygonButton =DEFAULT_CLOSE_POLYGON_BUTTON;
	}
	
	@SuppressWarnings("unchecked")
	public void start() throws Exception {
		
		Enumeration<CommPortIdentifier> portList = CommPortIdentifier.getPortIdentifiers();
		running =true;
		
		LoopingOverPorts:
		while( portList.hasMoreElements() ){
			if( ( port = portList.nextElement() ).getName().equalsIgnoreCase( communicationPort ) ) {
				break LoopingOverPorts;
			}
		}
		
		try {
			serialPort = (SerialPort)port.open( "TMap", 1000 );
			
			serialPort.setSerialPortParams( baudRate, dataBits, stopBits, parity );
			serialPort.disableReceiveFraming();
			serialPort.disableReceiveTimeout();
			serialPort.addEventListener(this);
			serialPort.notifyOnDataAvailable(true);			
			
			bytesAlrdyRead ="";

			inputStream =serialPort.getInputStream();	
			
		} catch (Exception e) {
			stop();
			throw new Exception( e );			
		}
		
		thread =new Thread(this);
		thread.setName( "DIGITIZER THREAD" );
		thread.setPriority(Thread.NORM_PRIORITY);		
		thread.start();		
	}

	public void stop() {
		try {		
			
			if ( inputStream !=null ) { 
				inputStream.close();
			}
			
			if( serialPort != null){
				serialPort.close();
			}
					
		} catch (Exception e) {
			
		}
		
		running =false;
		
	}	
			
	public synchronized void serialEvent(SerialPortEvent event) {
		  
		  switch ( event.getEventType() ) {
		  
		    //Break interrupt
		    case SerialPortEvent.BI: 
		    	logger.info("Break interrupt");
		    	break;	
		    	
		    // Overrun error
		    case SerialPortEvent.OE: 
		    	logger.info("Overrun error"); 
		    	break;
		    	
		    // Framing error
		    case SerialPortEvent.FE: 
		    	logger.info("Framing error");
			 	break;
			 	
		    // Parity error
		    case SerialPortEvent.PE: 
		    	logger.info("Parity error");
			 	break;
			 	
		    // Carrier detect
		    case SerialPortEvent.CD: 
		    	logger.info("Carrier detect");
			 	break;
			 	
		    // Clear to send
		    case SerialPortEvent.CTS: 
		    	logger.info("Clear to send");
			 	break;
			 	
		    // Data set ready
		    case SerialPortEvent.DSR: 
		    	logger.info("Data set ready");
			 	break;
			 	
		    // Ring indicator
		    case SerialPortEvent.RI: 
		    	logger.info("Ring indicator");
			 	break;
			 	
		    // Output buffer is empty
		    case SerialPortEvent.OUTPUT_BUFFER_EMPTY:
		    	logger.info("Buffer empty");
		    	break;
		    	
		    // Data available at the serial port
		    case SerialPortEvent.DATA_AVAILABLE:
		    	logger.info("Data available");
		     
		    	try {		  
				  
				  int nbAvailable =inputStream.available();
				  if ( nbAvailable > 0 ) {
					  
					  byte[] buffer =new byte[ nbAvailable ];  
					  int numberOfBytesRead = inputStream.read( buffer, 0, nbAvailable );
					  String current =bytesAlrdyRead + new String( buffer, 0, numberOfBytesRead );

					  int crPos =current.indexOf( lineSeparator );
					  
					  if ( crPos == -1 ) {
						  bytesAlrdyRead+=current;
						  break;
						  
					  } else {
						  bytesAlrdyRead =current.substring( crPos + 2 );
					  
						  current =current.substring( 0, crPos );
						  for( int ct =0; ct < lineSeparator.length(); ct++ ) {
							  current =current.replaceAll( String.valueOf( lineSeparator.charAt( ct ) ), "" );
						  }
					  
						  Scanner commaSpliter =new Scanner( current ).useDelimiter("\\s*" + fieldSeparator + "\\s*");
						  
						  if ( current != null && current.length() > 0 ) {
						     double x =commaSpliter.nextDouble();
						     double y =commaSpliter.nextDouble();
						     int currentButtonId =commaSpliter.nextInt();

						     int action =currentButtonId == Integer.parseInt( newPointButton ) ? DigitizerEntry.NEW_POINT_ACTION
						    		 														   : DigitizerEntry.CLOSE_POLYGON_ACTION;
						     
						     DigitizerEntry digitizerEntry =new DigitizerEntryImpl( x, yMax - y, action );
						     
						     logger.debug( "DigitizerEntry x=[" + x + "], y=[" + y + "], action=[" + action + "]" );
						     
						     //notifyObservers( digitizerEntry );
						     EventBus.publish( new DigitizerInput( digitizerEntry ) );
						  }
					  }
				  }
			     
		    	} catch ( IOException e ) { 
		    	  e.printStackTrace();
		    	  logger.error( e );
		    	}
		    	break;
		    	
		    default: 
		    	logger.info("DEFAULT");
			  	break;
		  }
		 
	}

	public void run() {
		
		if ( running ) {
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		} else {
			return;
		}
		
	}
	
	/*@Override
	public void notifyObservers(Object arg) {
		setChanged();
		super.notifyObservers(arg); // currentPoint
	}*/
	
	@Override
	protected void finalize() throws Throwable {
		super.finalize();

	}
	
	public boolean relativeCoordinatesSystemIsConfigured() {
		
		return xMin != Double.MAX_VALUE && yMin != Double.MAX_VALUE 
				&& xMax != Double.MIN_VALUE && yMax != Double.MIN_VALUE; 

	}
	
	public int getBaudRate() {
		return baudRate;
	}

	public String getCommunicationPort() {
		return communicationPort;
	}

	public int getDataBitsConfiguration() {
		return dataBits;
	}

	public int getParity() {
		return parity;
	}

	public void setBaudRate(int baudRate) {
		this.baudRate =baudRate;		
	}

	public void setCommunicationPort(String portName) {
		this.communicationPort =portName;
	}

	public void setDataBitsConfiguration(int dataBits) {
		this.dataBits =dataBits;
	}

	public void setParity(int parity) {
		this.parity =parity;		
	}	

	public int getStopBits() {
		return stopBits;
	}

	public void setStopBits(int stopBits) {
		this.stopBits = stopBits;
	}

	public double getXMin() {
		return xMin;
	}

	public void setXMin(double min) {
		xMin = min;
	}

	public double getYMin() {
		return yMin;
	}

	public void setYMin(double min) {
		yMin = min;
	}

	public double getXMax() {
		return xMax;
	}

	public void setXMax(double max) {
		xMax = max;
	}

	public double getYMax() {
		return yMax;
	}

	public void setYMax(double max) {
		yMax = max;
	}

	public String getFieldSeparator() {
		return fieldSeparator;
	}

	public void setFieldSeparator(String separator) {
		this.fieldSeparator = separator;
	}

	public String getLineSeparator() {
		return lineSeparator;
	}

	public void setLineSeparator(String lineSeparator) {
		this.lineSeparator = lineSeparator;
	}

	public String getNewPointButton() {
		return newPointButton;
	}

	public void setNewPointButton(String newPointButton) {
		this.newPointButton = newPointButton;
	}

	public String getClosePolygonButton() {
		return closePolygonButton;
	}

	public void setClosePolygonButton(String closePolygonButton) {
		this.closePolygonButton = closePolygonButton;
	}
	
}
